/**
 * Provides the classes necessary to create an Graph structure using java Collection Framework.
 * <p>
 * A "graph" in this context refers to a collection of vertices or 'nodes'
 * and a collection of edges that connect pairs of vertices.
 * A graph may be undirected, meaning that there is no distinction
 * between the two vertices associated with each edge, or its edges may be
 * directed from one vertex to another.
 * @see http://en.wikipedia.org/wiki/Graph_theory
 *
 * @since 1.6
 */

import java.util.HashMap;
import java.util.Set;

/**
 *
 * @author wallace.cunha
 */
public class Graph<E> {

    public static final short ORIENTED = 0;
    public static final short NO_ORIENTED = 1;
    private HashMap<E,HashMap<E,Integer>> nodes;

    public Graph(){
       this.nodes = new HashMap<E,HashMap<E,Integer>>();
    }
    
    /**
    *   Adiciona um novo nó ao grafo, quando um novo nó é adicionado
    *   não tm vertices adjacentes.
    *
    *   @param E node
    *
    *   E node - Tipo generico de objeto que será colocado como nó do grafo
    */

    public void addNode(E node){
        this.nodes.put(node,null);
    }

    public void removeNode(E node){
        if(!this.nodes.get(node).equals(null)){
            for(E nodeA : this.nodes.get(node).keySet()){
                this.nodes.get(nodeA).remove(node);
            }
        }
        this.nodes.remove(node);
    }

    public boolean isConnected(E nodeA, E nodeB){
        return !(this.nodes.get(nodeB).get(nodeA).equals(null) || this.nodes.get(nodeA).get(nodeB).equals(null));
    }

    public boolean isConnectedOriented(E nodeA, E nodeB){
        return !(this.nodes.get(nodeB).get(nodeA).equals(null));
    }

    public int getEdgeWeight(E nodeA, E nodeB){
        return this.nodes.get(nodeA).get(nodeB);
    }
    
    public Set getAllNode() {
       return this.nodes.keySet();
    }

    public Set getAllSubNodes(E NodeA){
        return this.nodes.get(NodeA).keySet();
    }

    public void addEdge(E nodeA,E nodeB, int orientation){
        if(this.nodes.containsKey(nodeA)&& this.nodes.containsKey(nodeB)){
            if(this.nodes.get(nodeA) == null){
                this.nodes.put(nodeA, new HashMap());
            }
            this.nodes.get(nodeA).put(nodeB,null);
            if(orientation == Graph.NO_ORIENTED)
                this.addEdge(nodeB, nodeA, Graph.ORIENTED);
        }
    }

    public void addEdge(E nodeA,E nodeB,int weight ,int orientation){
       if(this.nodes.containsKey(nodeA)&& this.nodes.containsKey(nodeB)){
            if(this.nodes.get(nodeA) == null){
                this.nodes.put(nodeA, new HashMap());
            }
            this.nodes.get(nodeA).put(nodeB,weight);
            if(orientation == Graph.NO_ORIENTED)
                this.addEdge(nodeB, nodeA, weight, Graph.ORIENTED);
        }
    }

    public void removeEdge(E nodeA,E nodeB, int orientation){
        if(this.nodes.containsKey(nodeA)&& this.nodes.containsKey(nodeB)){
            if(this.nodes.get(nodeA) != null){
                return;
            }
            this.nodes.get(nodeA).remove(nodeB);
            if(orientation == Graph.NO_ORIENTED)
                this.removeEdge(nodeB, nodeA, Graph.ORIENTED);
        }
    }
}